#include "vCL.h"

////    TEST CASE    //////////////////////////////////////////

/**
int main() {
	init_CL();
	load_CL("./Ktest.cl");
	kernel* thisK = find_CL("test");
	buffer inB = initBuffer(512*sizeof(int), INPUT_BUFFER);
	buffer outB = initBuffer(512*sizeof(int), OUTPUT_BUFFER);
	
	int* inD = (int*)inB.data;
	int* outD = (int*)outB.data;

	for (int i = 0; i < 512; i++)
		inD[i] = i;

	writeBuffer(&inB);
	call_CL(thisK, 512, 64, 1, "bb", &inB, &outB);
	readBuffer(&outB);

	for (int i = 0; i < 10; i++)
		printf("in: %i, out: %i\n", inD[i], outD[i]);

	return EXIT_SUCCESS;
}/**/

////    ENGINE    /////////////////////////////////////////////

void init_CL(void) {
	int n, err;
	char INFO[1024];

	err = clGetPlatformIDs(1, &(CL.Plt), 0);
	if (err < 0) error_CL(err);

	err = clGetDeviceIDs(CL.Plt, CL_DEVICE_TYPE_GPU, 1, &(CL.Dev), 0);
	if (err < 0) error_CL(err);
	
	CL.Ctx = clCreateContext(NULL, 1, &(CL.Dev), 0, 0, &err);
	if (err < 0) error_CL(err);

	CL.Cmd = clCreateCommandQueue(CL.Ctx, CL.Dev, 0, &err);
	if (err < 0) error_CL(err);

	printf("---------------------------\
	\nSimplified OpenCL Interface\n\
	evenex_code\n- - - - - - - - - - - - - -\n");
	clGetPlatformInfo(CL.Plt, CL_PLATFORM_VERSION, 1024, INFO, 0);
	printf("%s\n", INFO);
	clGetDeviceInfo(CL.Dev, CL_DEVICE_NAME, 1024, INFO, 0);
	printf("%s GPU\n---------------------------\n", INFO);

	if (err < 0) {
		printf("FATAL ERROR: Could not initialize OpenCL!\n");
		exit(EXIT_FAILURE);
	}
	return;
}

void load_CL(char* _path) {
	int err;

	cl_program P;
	char* SRC;
	loadSource(_path, &SRC);
	P = clCreateProgramWithSource(CL.Ctx, 1, &SRC, 0, &err);
	if (err < 0) error_CL(err);
	free(SRC);

	err = clBuildProgram(P, 0, 0, 0, 0, 0);
	if (err < 0) {
		char* LOG = (char*) malloc (sizeof(char) * 4096);
		clGetProgramBuildInfo(P, CL.Dev, CL_PROGRAM_BUILD_LOG, 4096, LOG, 0);
		printf("Log:\n%s", LOG);
		free(LOG);
		error_CL(err);
	}

	err = clCreateKernelsInProgram(P, 0, 0, &(CL.n_K));
	if (err < 0) error_CL(err);

	cl_kernel* Ks;
	Ks = (cl_kernel*) malloc (sizeof(cl_kernel) * CL.n_K);
	err = clCreateKernelsInProgram(P, CL.n_K, Ks, 0);
	if (err < 0) error_CL(err);

	CL.K = (kernel*) malloc (sizeof(kernel) * CL.n_K);
	for (int i = 0; i < CL.n_K; i++) {
		CL.K[i].kernel = Ks[i];
		err = clGetKernelInfo(CL.K[i].kernel, CL_KERNEL_FUNCTION_NAME, 128, CL.K[i].name, 0);
		if (err < 0) error_CL(err);
		err = clGetKernelInfo(CL.K[i].kernel, CL_KERNEL_NUM_ARGS, sizeof(int), &(CL.K[i].args), 0);
		if (err < 0) error_CL(err);
	}
	free(Ks);

	clReleaseProgram(P);

	return;
}

kernel* find_CL(char* _name) {
	for (int i = 0; i < CL.n_K; i++) {
		if (strcmp(_name, CL.K[i].name) == 0) {
			return &(CL.K[i]);
		}
	}
	printf("Error: Kernel Not Found: %s\n", _name);
	return NULL;
}

void call_CL(kernel* _K, size_t _gs, size_t _ls, int _set, ...) {
	int err;
	cl_event ev_done;

	if (_set) {	
		va_list args;
		char* 	_typelist;
		void* 	address;
		int	size;

		va_start(args, _set);

		_typelist = va_arg(args, char*);
		int n = (*_K).args;
		for (int i = 0; i < n; i++) {
			address = va_arg(args, void*);
			switch (_typelist[i]) {
				case 'f':
					size = sizeof(float);	
				break;
				case 'c':
					size = sizeof(char);	
				break;
				case 'i':
					size = sizeof(int);	
				break;
				case 'b':
					size = sizeof(cl_mem);
					address = (void*)&((*(buffer*)address).vram);
				break;
			}
			err = clSetKernelArg((*_K).kernel, i, size, address);
			if (err < 0) error_CL(err);
		}
		va_end(args);
	}
	
	if (_ls)
		err = clEnqueueNDRangeKernel(CL.Cmd, (*_K).kernel, 1, 0, &_gs, &_ls, 0, 0, &ev_done);
	else
		err = clEnqueueNDRangeKernel(CL.Cmd, (*_K).kernel, 1, 0, &_gs, 0, 0, 0, &ev_done);
	if (err < 0) error_CL(err);

	err = clWaitForEvents(1, &ev_done);
	if (err < 0) error_CL(err);

	return;
}

void sync_CL(void) {
	clFinish(CL.Cmd);
	return;
}

void kill_CL(void) {
	sync_CL();
	int err;

	for (int i = 0; i < CL.n_K; i++) {
		err = clReleaseKernel(CL.K[i].kernel);
		if (err < 0) error_CL(err);
	}
	err = clReleaseCommandQueue(CL.Cmd);
	if (err < 0) error_CL(err);
	err = clReleaseContext(CL.Ctx);
	if (err < 0) error_CL(err);

	return;
}

////    DATA    ///////////////////////////////////////////////

buffer initBuffer(int _size, int _mode) {
	int err;
	buffer ret;

	switch (_mode) {
		case IO_BUFFER:
		ret.vram = clCreateBuffer(CL.Ctx, CL_MEM_READ_WRITE, _size, NULL, &err);
		ret.data = malloc(_size);
		break;
		case OUTPUT_BUFFER:
		ret.vram = clCreateBuffer(CL.Ctx, CL_MEM_WRITE_ONLY, _size, NULL, &err);
		ret.data = malloc(_size);
		break;
		case INPUT_BUFFER:
		ret.vram = clCreateBuffer(CL.Ctx, CL_MEM_READ_ONLY, _size, NULL, &err);
		ret.data = malloc(_size);
		break;
		case INTERNAL_BUFFER:
		ret.vram = clCreateBuffer(CL.Ctx, CL_MEM_READ_WRITE, _size, NULL, &err);
		ret.data = NULL;
		break;
	}

	ret.size = _size;
	ret.mode = _mode;
	if (err < 0) error_CL(err);

	return ret;
}

void readBuffer(buffer* _b) {
	int err;
	cl_event ev_done;

	if ((*_b).mode == INTERNAL_BUFFER) {
		printf("Error: Attempted to read from INTERNAL buffer!\n");
		return;
	}
	if ((*_b).mode == INPUT_BUFFER) {
		printf("Error: Attempted to read from INPUT buffer!\n");
		return;
	}

	err = clEnqueueReadBuffer(
		CL.Cmd, (*_b).vram, 0, 0, (*_b).size, (*_b).data, 0, 0, &ev_done
		);
	if (err < 0) error_CL(err);

	err = clWaitForEvents(1, &ev_done);
	if (err < 0) error_CL(err);

	return;
}

void writeBuffer(buffer* _b) {
	int err;
	cl_event ev_done;

	err = clEnqueueWriteBuffer(
		CL.Cmd, (*_b).vram, 0, 0, (*_b).size, (*_b).data, 0, 0, &ev_done
		);
	if (err < 0) error_CL(err);

	err = clWaitForEvents(1, &ev_done);
	if (err < 0) error_CL(err);

	return;
}

void freeBuffer(buffer* _b) {
	int err;
	err = clReleaseMemObject((*_b).vram);
	if (err < 0) error_CL(err);

	if ( (*_b).data != NULL)
		free((*_b).data);

	return;
}

////    UTILITIES    //////////////////////////////////////////

void error_CL(int _err) {
	char* flag;
	switch (_err) {
		case -1:
		flag = "CL_DEVICE_NOT_FOUND";
		break;
		case -2:
		flag = "CL_DEVICE_NOT_AVAILABLE";
		break;
		case -3:
		flag = "CL_COMPILER_NOT_AVAILABLE";
		break;
		case -4:
		flag = "CL_MEM_OBJECT_ALLOCATION_FAILURE";
		break;
		case -5:
		flag = "CL_OUT_OF_RESOURCES";
		break;
		case -6:
		flag = "CL_OUT_OF_HOST_MEMORY";
		break;
		case -7:
		flag = "CL_PROFILING_INFO_NOT_AVAILABLE";
		break;
		case -8:
		flag = "CL_MEM_COPY_OVERLAP";
		break;
		case -9:
		flag = "CL_IMAGE_FORMAT_MISMATCH";
		break;
		case -10:
		flag = "CL_IMAGE_FORMAT_NOT_SUPPORTED";
		break;
		case -11:
		flag = "CL_BUILD_PROGRAM_FAILURE";
		break;
		case -12:
		flag = "CL_MAP_FAILURE";
		break;
		case -13:
		flag = "CL_MISALIGNED_SUB_BUFFER_OFFSET";
		break;
		case -14:
		flag = "CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST";
		break;
		case -15:
		flag = "CL_COMPILE_PROGRAM_FAILURE";
		break;
		case -16:
		flag = "CL_LINKER_NOT_AVAILABLE";
		break;
		case -17:
		flag = "CL_LINK_PROGRAM_FAILURE";
		break;
		case -18:
		flag = "CL_DEVICE_PARTITION_FAILED";
		break;
		case -19:
		flag = "CL_KERNEL_ARG_INFO_NOT_AVAILABLE";
		break;
		case -30:
		flag = "CL_INVALID_VALUE";
		break;
		case -31:
		flag = "CL_INVALID_DEVICE_TYPE";
		break;
		case -32:
		flag = "CL_INVALID_PLATFORM";
		break;
		case -33:
		flag = "CL_INVALID_DEVICE";
		break;
		case -34:
		flag = "CL_INVALID_CONTEXT";
		break;
		case -35:
		flag = "CL_INVALID_QUEUE_PROPERTIES";
		break;
		case -36:
		flag = "CL_INVALID_COMMAND_QUEUE";
		break;
		case -37:
		flag = "CL_INVALID_HOST_PTR";
		break;
		case -38:
		flag = "CL_INVALID_MEM_OBJECT";
		break;
		case -39:
		flag = "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR";
		break;
		case -40:
		flag = "CL_INVALID_IMAGE_SIZE";
		break;
		case -41:
		flag = "CL_INVALID_SAMPLER";
		break;
		case -42:
		flag = "CL_INVALID_BINARY";
		break;
		case -43:
		flag = "CL_INVALID_BUILD_OPTIONS";
		break;
		case -44:
		flag = "CL_INVALID_PROGRAM";
		break;
		case -45:
		flag = "CL_INVALID_PROGRAM_EXECUTABLE";
		break;
		case -46:
		flag = "CL_INVALID_KERNEL_NAME";
		break;
		case -47:
		flag = "CL_INVALID_KERNEL_DEFINITION";
		break;
		case -48:
		flag = "CL_INVALID_KERNEL";
		break;
		case -49:
		flag = "CL_INVALID_ARG_INDEX";
		break;
		case -50:
		flag = "CL_INVALID_ARG_VALUE";
		break;
		case -51:
		flag = "CL_INVALID_ARG_SIZE";
		break;
		case -52:
		flag = "CL_INVALID_KERNEL_ARGS";
		break;
		case -53:
		flag = "CL_INVALID_WORK_DIMENSION";
		break;
		case -54:
		flag = "CL_INVALID_WORK_GROUP_SIZE";
		break;
		case -55:
		flag = "CL_INVALID_WORK_ITEM_SIZE";
		break;
		case -56:
		flag = "CL_INVALID_GLOBAL_OFFSET";
		break;
		case -57:
		flag = "CL_INVALID_EVENT_WAIT_LIST";
		break;
		case -58:
		flag = "CL_INVALID_EVENT";
		break;
		case -59:
		flag = "CL_INVALID_OPERATION";
		break;
		case -60:
		flag = "CL_INVALID_GL_OBJECT";
		break;
		case -61:
		flag = "CL_INVALID_BUFFER_SIZE";
		break;
		case -62:
		flag = "CL_INVALID_MIP_LEVEL";
		break;
		case -63:
		flag = "CL_INVALID_GLOBAL_WORK_SIZE";
		break;
		case -64:
		flag = "CL_INVALID_PROPERTY";
		break;
		case -65:
		flag = "CL_INVALID_IMAGE_DESCRIPTOR";
		break;
		case -66:
		flag = "CL_INVALID_COMPILER_OPTIONS";
		break;
		case -67:
		flag = "CL_INVLID_LINKER_OPTIONS";
		break;
		case -68:
		flag = "CL_INVALID_DEVICE_PARTITION_COUNT";
		break;
		default:
		flag = "UNKNOWN ERROR";
		break;
	}
	printf("%s\n", flag);
	exit(EXIT_FAILURE);
}

void loadSource(char* _filename, char** _SRC) {
	int len;
	FILE* fSRC = fopen(_filename, "r");
	if (fSRC == NULL) {
		printf("FATAL ERROR: Bad source!\n");
		exit(EXIT_FAILURE);
	}
	fseek(fSRC, 0, SEEK_END);
	len = ftell (fSRC);
	rewind(fSRC);

	*_SRC = (char*) malloc ( sizeof(char)*(len) );
	fread(*_SRC, 1, len, fSRC);
	fclose(fSRC);

	len--;
	while ( *(*_SRC + len) != 0x7D && len > 0 )
		len--;
	len++;
	*(*_SRC + len) = '\0';
	return;
}
